import { logger } from '../../../types/constants'
import { ToolKit } from '../../utils/toolkit'

export interface FetchOperationHeaders {
  [key: string]: string
}

/**
 * Fetch 操作配置
 */
export interface FetchOperationConfig {
  url: string
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
  encoding?: string // 强制编码
  detectEncoding?: boolean // 是否自动检测编码
  timeout?: number
  headers?: FetchOperationHeaders
  body?: any
  responseType?: XMLHttpRequestResponseType
  retryCount?: number
  retryDelay?: number
  credentials?: RequestCredentials
  mode?: RequestMode
}

/**
 * Fetch 操作结果
 */
export interface FetchOperationResult<T = any> {
  success: boolean
  data?: T
  error?: Error
  status?: number
  statusText?: string
  duration: number
  retryCount: number
  headers?: Record<string, string>
}

/**
 * 默认 Fetch 操作配置
 */
const DEFAULT_FETCH_OPERATION_CONFIG: Required<FetchOperationConfig> = {
  url: '',
  method: 'GET',
  encoding: 'utf-8',
  detectEncoding: true,
  timeout: 5000,
  retryCount: 2,
  retryDelay: 1000,
  headers: {},
  body: undefined,
  responseType: 'json',
  credentials: 'same-origin',
  mode: 'cors',
}

/**
 * Fetch 操作器
 */
export class FetchOperator {
  private readonly name: string = 'FetchOperator'
  private static instance: FetchOperator
  private readonly charset: string = ToolKit.ui.page.getCharsetFromMetaTags()

  public static getInstance(): FetchOperator {
    if (!FetchOperator.instance) {
      FetchOperator.instance = new FetchOperator()
    }
    return FetchOperator.instance
  }

  /**
   * 获取 HTML 文档内容
   */
  public async fetchHtmlDocument(
    url: string,
    options?: Partial<FetchOperationConfig>,
  ): Promise<FetchOperationResult<Document>> {
    const config: FetchOperationConfig = {
      url,
      responseType: 'text',
      method: 'GET',
      headers: {
        Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        ...(options?.headers || {}),
      },
      ...options,
      ...(!options?.encoding && options?.detectEncoding ? { encoding: this.charset } : {}),
    }

    const result = await this.execute<string>(config, 'fetchHtmlDocument')

    if (!result.success || !result.data) {
      return {
        ...result,
        data: undefined,
      }
    }

    try {
      // 将 HTML 文本转换为 Document 对象
      const parser = new DOMParser()
      const doc = parser.parseFromString(result.data, 'text/html')

      return {
        ...result,
        data: doc,
      }
    }
    catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error : new Error('HTML 解析失败'),
        duration: result.duration,
        retryCount: result.retryCount,
      }
    }
  }

  /**
   * 执行 Fetch 操作
   */
  public async execute<T>(
    config: FetchOperationConfig,
    operationType: string = 'fetchOperation',
  ): Promise<FetchOperationResult<T>> {
    const mergedConfig = ToolKit.config.mergeConfig(DEFAULT_FETCH_OPERATION_CONFIG, config)

    logger.debug(`[${this.name}] 配置信息：${mergedConfig.url}`, mergedConfig)

    const { url, method, retryCount: maxRetries, retryDelay } = mergedConfig
    const shortenUrl = ToolKit.browser.url.shortenUrl(url)

    const startTime = Date.now()
    let retryCount = 0

    while (retryCount <= maxRetries) {
      try {
        const result = await this.executeSingleFetch<T>(mergedConfig)
        const duration = Date.now() - startTime

        logger.debug(`[${this.name}] ${operationType} 操作成功`, {
          operationType,
          url: shortenUrl,
          method,
          status: result.status,
          retryCount,
          duration: `${duration}ms`,
        })

        return {
          success: true,
          data: result.data,
          status: result.status,
          statusText: result.statusText,
          headers: result.headers,
          retryCount,
          duration,
        }
      }
      catch (error) {
        retryCount++

        if (retryCount > maxRetries) {
          const duration = Date.now() - startTime

          logger.warn(`[${this.name}] ${operationType} 操作失败`, {
            operationType,
            url: shortenUrl,
            error: error instanceof Error ? error.message : String(error),
            retryCount: maxRetries,
            duration: `${duration}ms`,
          })

          return {
            success: false,
            error: error instanceof Error ? error : new Error(String(error)),
            retryCount: maxRetries,
            duration,
          }
        }

        logger.error(`[${this.name}] ${operationType} 操作重试`, {
          operationType,
          url: shortenUrl,
          retryCount,
          maxRetries,
          delay: `${retryDelay}ms`,
        })

        await ToolKit.async.delay(retryDelay)
      }
    }

    return {
      success: false,
      error: new Error('所有重试尝试都失败'),
      retryCount,
      duration: Date.now() - startTime,
    }
  }

  /**
   * 执行单次 Fetch 操作
   */
  private async executeSingleFetch<T>(
    config: Required<FetchOperationConfig>,
  ): Promise<{
    data: T
    status: number
    statusText: string
    headers: Record<string, string>
  }> {
    const { url, encoding, method, headers: requestHeaders, timeout, body, responseType, credentials, mode } = config

    const controller = new AbortController()
    const timeoutId = setTimeout(() => {
      controller.abort()
    }, timeout)

    try {
      const response = await fetch(url, {
        method,
        headers: requestHeaders,
        body: body ? JSON.stringify(body) : undefined,
        credentials,
        mode,
        signal: controller.signal,
      })

      clearTimeout(timeoutId)

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }

      // 根据响应类型处理数据
      let data: any
      switch (responseType) {
        case 'json':
          data = await response.json()
          break
        case 'text':
          data = await this.processTextResponse(response, encoding)
          break
        case 'blob':
          data = await response.blob()
          break
        case 'arraybuffer':
          data = await response.arrayBuffer()
          break
        default:
          data = await response.text()
      }

      // 提取响应头
      const headers: Record<string, string> = {}
      response.headers.forEach((value, key) => {
        headers[key] = value
      })

      return {
        data,
        status: response.status,
        statusText: response.statusText,
        headers,
      }
    }
    catch (error) {
      clearTimeout(timeoutId)
      if (error instanceof Error && error.name === 'AbortError') {
        throw new Error(`请求超时 (${timeout}ms)`)
      }
      throw error
    }
  }

  /**
   * 处理文本响应（支持编码转换）
   */
  private async processTextResponse(response: Response, encoding: string): Promise<string> {
    // 获取ArrayBuffer进行编码处理
    const arrayBuffer = await response.arrayBuffer()

    try {
      // 使用指定的编码解码
      const decoder = new TextDecoder(encoding)
      return decoder.decode(arrayBuffer)
    }
    catch (error) {
      // 如果指定编码失败，尝试自动检测编码
      logger.warn(`[${this.name}] 指定编码解码失败，尝试自动检测`, {
        encoding,
        error: error instanceof Error ? error.message : String(error),
      })

      return this.autoDetectAndDecode(arrayBuffer, encoding)
    }
  }

  /**
   * 自动检测并解码
   */
  private autoDetectAndDecode(arrayBuffer: ArrayBuffer, fallbackEncoding: string): string {
    const encodingsToTry = [
      fallbackEncoding,
      'utf-8',
      'gbk',
      'gb2312',
      'big5',
      'shift_jis',
      'iso-8859-1',
    ]

    for (const encoding of encodingsToTry) {
      try {
        const decoder = new TextDecoder(encoding)
        const result = decoder.decode(arrayBuffer)

        // 检查是否包含乱码
        if (!this.containsGarbledText(result)) {
          logger.debug(`[${this.name}] 自动检测编码成功`, { encoding })
          return result
        }
      }
      catch {
        continue
      }
    }

    // 所有编码都失败，使用UTF-8作为最后手段
    const decoder = new TextDecoder('utf-8')
    return decoder.decode(arrayBuffer)
  }

  /**
   * 检测是否包含乱码
   */
  private containsGarbledText(text: string): boolean {
    const garbledPatterns = [
      /����/g,
      /[\uFFFD\uFFFE\uFFFF]/g,
    ]
    return garbledPatterns.some(pattern => pattern.test(text))
  }
}
